#include "classfile.h"

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void endianswitch(char *buf, int bytes)
{
	int i;
	char x;
	/* we MUST have an even number of bytes */
	assert(!(bytes & 1));
	for(i = 0; i < bytes / 2; ++i)
	{
		x = buf[i];
		buf[i] = buf[bytes - i - 1];
		buf[bytes - i - 1] = x;
	}
}

void janno_element_read(char *bytes,
		Annotations_elementvalue *eval, int *szread)
{
	int i;
	int j;

	*szread = 0;
	eval->tag = *(bytes + *szread);
	*szread += 1;
	switch(eval->tag)
	{
		/*+++++++++++++++ Primitive Types +++++++++++++++++*/
		/* (index into constant pool) */
		case 'B': /* byte    */
		case 'C': /* char    */
		case 'D': /* double  */
		case 'J': /* long    */
		case 'F': /* float   */
		case 'I': /* int     */
		case 'Z': /* boolean */
		case 'S': /* short   */
		case 's': /* string  */
			eval->value.const_value_index =
				ntohs(*(short*)(bytes + *szread));
			*szread += 2;
			break;
		/*+++++++++++++++ Enum            +++++++++++++++++*/
		case 'e':
			eval->value.enum_const_value.type_name_index =
				ntohs(*(short*)(bytes + *szread));
			*szread += 2;
			eval->value.enum_const_value.const_name_index =
				ntohs(*(short*)(bytes + *szread));
			*szread += 2;
			break;
		/*+++++++++++++++ Class           +++++++++++++++++*/
		case 'c':
			eval->value.class_info_index =
				ntohs(*(short*)(bytes + *szread));
			*szread += 2;
			break;
		/*+++++++++++++++ Nested Annotation +++++++++++++++*/
		case '@':
			eval->value.annotation_value =
				malloc(sizeof(Annotations_annotation));
			memset(eval->value.annotation_value, 0,
					sizeof(Annotations_annotation));
			janno_read(bytes + *szread, eval->value.annotation_value, &i);
			*szread += i;
			break;
		/*+++++++++++++++ Array           +++++++++++++++++*/
		case '[':
			eval->value.array_value.num_values =
				ntohs(*(short*)(bytes + *szread));
			*szread += 2;
			if(eval->value.array_value.num_values)
			{
				i = eval->value.array_value.num_values *
					sizeof(Annotations_elementvalue);
				eval->value.array_value.values = malloc(i);
				memset(eval->value.array_value.values, 0, i);
				for(i = 0; i < eval->value.array_value.num_values; ++i)
				{
					janno_element_read(bytes + *szread,
							&eval->value.array_value.values[i], &j);
					*szread += j;
				}
			}
			break;
		default:
			fprintf(stderr, "Unknown annotation element tag '%x' at %s:%d\n",
					eval->tag, __FILE__, __LINE__);
	}
}

void janno_read(char *bytes, Annotations_annotation *ann, int *szread)
{
	int i;
	int j;
	*szread = 0;
	ann->type_index = ntohs(*(short*)(bytes + *szread));
	*szread += 2;
	ann->num_element_value_pairs = ntohs(*(short*)(bytes + *szread));
	*szread += 2;
	if(ann->num_element_value_pairs)
	{
		i = ann->num_element_value_pairs *
			sizeof(Annotations_elementvaluepair);
		ann->element_value_pairs = malloc(i);
		memset(ann->element_value_pairs, 0, i);
		for(i = 0; i < ann->num_element_value_pairs; ++i)
		{
			Annotations_elementvaluepair *evp =
				&ann->element_value_pairs[i];
			evp->element_name_index =
				ntohs(*(short*)(bytes + *szread));
			*szread += 2;
			janno_element_read(bytes + *szread, &evp->value, &j);
			*szread += j;
		}
	}
}

void janno_attr_read(char *bytes,
		RuntimeAnnotations_attribute *attr, int *szread)
{
	int i;
	int j;
	*szread = 0;
	attr->num_annotations = ntohs(*(short*)(bytes + *szread));
	*szread += 2;
	if(attr->num_annotations)
	{
		int allocsize = attr->num_annotations *
			sizeof(Annotations_annotation);
		attr->annotations = malloc(allocsize);
		memset(attr->annotations, 0, allocsize);
		for(i = 0; i < attr->num_annotations; ++i)
		{
			janno_read(bytes + *szread, &attr->annotations[i], &j);
			*szread += j;
		}
	}
}

void jattr_read(jcf *cf, char *bytes, attr *attrs, int cnt, int *szread)
{
	int i;
	int j;
	int k;
	char *type;

	*szread = 0;

	for(i = 0; i < cnt; ++i)
	{
		attrs[i].nameidx = ntohs(*(short*)(bytes + *szread));
		*szread += 2;
		attrs[i].len = ntohl(*(int*)(bytes + *szread));
		*szread += 4;
		type = cf->cpool[attrs[i].nameidx].info.str.bytes;
		attrs[i].type = type;

		if(!strcmp(type, "ConstantValue"))
		{
			ConstantValue_attribute *at =
				malloc(sizeof(ConstantValue_attribute));
			attrs[i].info = at;
			at->constantvalue_index = ntohs(*(short*)(bytes + *szread));
			*szread += 2;
		}
		else if(!strcmp(type, "Code"))
		{
			Code_attribute *at = malloc(sizeof(Code_attribute));
			memset(at, 0, sizeof(Code_attribute));
			attrs[i].info = at;
			at->max_stack = ntohs(*(short*)(bytes + *szread));
			*szread += 2;
			at->max_locals = ntohs(*(short*)(bytes + *szread));
			*szread += 2;
			at->code_length = ntohl(*(int*)(bytes + *szread));
			*szread += 4;
			at->code = malloc(at->code_length);
			memcpy(at->code, bytes + *szread, at->code_length);
			*szread += at->code_length;
			at->exception_table_length = ntohs(*(short*)(bytes + *szread));
			*szread += 2;
			if(at->exception_table_length)
			{
				at->exception_table = malloc(at->exception_table_length *
						sizeof(Code_exception_tbl));
				for(j = 0; j < at->exception_table_length; ++j)
				{
					Code_exception_tbl *ex = &at->exception_table[j];
					ex->start_pc = ntohs(*(short*)(bytes + *szread));
					*szread += 2;
					ex->end_pc = ntohs(*(short*)(bytes + *szread));
					*szread += 2;
					ex->handler_pc = ntohs(*(short*)(bytes + *szread));
					*szread += 2;
					ex->catch_type = ntohs(*(short*)(bytes + *szread));
					*szread += 2;
				}
			}
			at->attributes_count = ntohs(*(short*)(bytes + *szread));
			*szread += 2;
			if(at->attributes_count)
			{
				at->attributes = malloc(at->attributes_count * sizeof(attr));
				memset(at->attributes, 0, at->attributes_count * sizeof(attr));
				jattr_read(cf, bytes + *szread, at->attributes,
						at->attributes_count, &j);
				*szread += j;
			}
		}
		else if(!strcmp(type, "Exceptions"))
		{
			Exceptions_attribute *at = malloc(sizeof(Exceptions_attribute));
			memset(at, 0, sizeof(Exceptions_attribute));
			attrs[i].info = at;
			at->number_of_exceptions = ntohs(*(short*)(bytes + *szread));
			*szread += 2;
			if(at->number_of_exceptions)
			{
				at->exception_index_table =
					malloc(at->number_of_exceptions * sizeof(short));
				for(j = 0; j < at->number_of_exceptions; ++j)
				{
					at->exception_index_table[j] =
						ntohs(*(short*)(bytes + *szread));
					*szread += 2;
				}
			}
		}
		else if(!strcmp(type, "InnerClasses"))
		{
			InnerClasses_attribute *at =
				malloc(sizeof(InnerClasses_attribute));
			memset(at, 0, sizeof(InnerClasses_attribute));
			attrs[i].info = at;
			at->number_of_classes = ntohs(*(short*)(bytes + *szread));
			*szread += 2;
			if(at->number_of_classes)
			{
				at->classes = malloc(at->number_of_classes *
						sizeof(InnerClass_class));
				for(j = 0; j < at->number_of_classes; ++j)
				{
					InnerClass_class *cl = &at->classes[j];
					cl->inner_class_info_index =
						ntohs(*(short*)(bytes + *szread));
					*szread += 2;
					cl->outer_class_info_index =
						ntohs(*(short*)(bytes + *szread));
					*szread += 2;
					cl->inner_name_index = ntohs(*(short*)(bytes + *szread));
					*szread += 2;
					cl->inner_class_access_flags =
						ntohs(*(short*)(bytes + *szread));
					*szread += 2;
				}
			}
		}
		else if(!strcmp(type, "Synthetic"))
		{
			attrs[i].info = NULL;
		}
		else if(!strcmp(type, "SourceFile"))
		{
			SourceFile_attribute *at = malloc(sizeof(SourceFile_attribute));
			attrs[i].info = at;
			at->sourcefile_index = ntohs(*(short*)(bytes + *szread));
			*szread += 2;
		}
		else if(!strcmp(type, "LineNumberTable"))
		{
			LineNumberTable_attribute *at =
				malloc(sizeof(LineNumberTable_attribute));
			memset(at, 0, sizeof(LineNumberTable_attribute));
			attrs[i].info = at;
			at->line_number_table_length = ntohs(*(short*)(bytes + *szread));
			*szread += 2;
			if(at->line_number_table_length)
			{
				at->line_number_table = malloc(at->line_number_table_length *
						sizeof(LineNumberTable_linenumber));
				for(j = 0; j < at->line_number_table_length; ++j)
				{
					LineNumberTable_linenumber *ln = &at->line_number_table[j];
					ln->start_pc = ntohs(*(short*)(bytes + *szread));
					*szread += 2;
					ln->line_number = ntohs(*(short*)(bytes + *szread));
					*szread += 2;
				}
			}
		}
		else if(!strcmp(type, "LocalVariableTable"))
		{
			LocalVariableTable_attribute *at =
				malloc(sizeof(LocalVariableTable_attribute));
			memset(at, 0, sizeof(LocalVariableTable_attribute));
			attrs[i].info = at;
			at->local_variable_table_length =
				ntohs(*(short*)(bytes + *szread));
			*szread += 2;
			if(at->local_variable_table_length)
			{
				at->local_variable_table =
					malloc(at->local_variable_table_length *
							sizeof(LocalVariableTable_localvariable));
				for(j = 0; j < at->local_variable_table_length; ++j)
				{
					LocalVariableTable_localvariable *lv =
						&at->local_variable_table[j];
					lv->start_pc = ntohs(*(short*)(bytes + *szread));
					*szread += 2;
					lv->length = ntohs(*(short*)(bytes + *szread));
					*szread += 2;
					lv->name_index = ntohs(*(short*)(bytes + *szread));
					*szread += 2;
					lv->descriptor_index = ntohs(*(short*)(bytes + *szread));
					*szread += 2;
					lv->index = ntohs(*(short*)(bytes + *szread));
					*szread += 2;
				}
			}
		}
		else if(!strcmp(type, "Deprecated"))
		{
			attrs[i].info = NULL;
		}
		else if(!strcmp(type, "EnclosingMethod"))
		{
			EnclosingMethod_attribute *at =
				malloc(sizeof(EnclosingMethod_attribute));
			attrs[i].info = at;
			at->class_index = ntohs(*(short*)(bytes + *szread));
			*szread += 2;
			at->method_index = ntohs(*(short*)(bytes + *szread));
			*szread += 2;
		}
		else if(!strcmp(type, "Signature"))
		{
			Signature_attribute *at = malloc(sizeof(Signature_attribute));
			attrs[i].info = at;
			at->signature_index = ntohs(*(short*)(bytes + *szread));
			*szread += 2;
		}
		else if(!strcmp(type, "SourceDebugExtension"))
		{
			SourceDebugExtension_attribute *at =
				malloc(sizeof(SourceDebugExtension_attribute));
			attrs[i].info = at;
			memcpy(at->debug_extension, bytes + *szread, attrs[i].len);
			*szread += attrs[i].len;
		}
		else if(!strcmp(type, "LocalVariableTypeTable"))
		{
			LocalVariableTypeTable_attribute *at =
				malloc(sizeof(LocalVariableTypeTable_attribute));
			memset(at, 0, sizeof(LocalVariableTypeTable_attribute));
			attrs[i].info = at;
			at->local_variable_type_table_length =
				ntohs(*(short*)(bytes + *szread));
			*szread += 2;
			if(at->local_variable_type_table_length)
			{
				at->local_variable_type_table =
					malloc(at->local_variable_type_table_length *
							sizeof(LocalVariableTypeTable_localvariabletype));
				for(j = 0; j < at->local_variable_type_table_length; ++j)
				{
					LocalVariableTypeTable_localvariabletype *lvt =
						&at->local_variable_type_table[j];
					lvt->start_pc = ntohs(*(short*)(bytes + *szread));
					*szread += 2;
					lvt->length = ntohs(*(short*)(bytes + *szread));
					*szread += 2;
					lvt->name_index = ntohs(*(short*)(bytes + *szread));
					*szread += 2;
					lvt->signature_index = ntohs(*(short*)(bytes + *szread));
					*szread += 2;
					lvt->index = ntohs(*(short*)(bytes + *szread));
					*szread += 2;
				}
			}
		}
		else if(strcmp(type, "RuntimeVisibleAnnotations") == 0 ||
				strcmp(type, "RuntimeInvisibleAnnotations") == 0)
		{
			RuntimeAnnotations_attribute *at =
				malloc(sizeof(RuntimeAnnotations_attribute));
			memset(at, 0, sizeof(RuntimeAnnotations_attribute));
			attrs[i].info = at;
			janno_attr_read(bytes + *szread, at, &j);
			*szread += j;
		}
		else if(!strcmp(type, "RuntimeVisibleParameterAnnotations"))
		{
			RuntimeVisibleParameterAnnotations_attribute *at =
				malloc(sizeof(RuntimeVisibleParameterAnnotations_attribute));
			memset(at, 0,
					sizeof(RuntimeVisibleParameterAnnotations_attribute));
			attrs[i].info = at;
			at->num_parameters = *bytes;
			(*szread)++;
			for(j = 0; j < at->num_parameters; ++j)
			{
				janno_attr_read(bytes + *szread,
						&at->parameter_annotations, &k);
				*szread += k;
			}
		}
		else if(!strcmp(type, "AnnotationDefault"))
		{
			AnnotationDefault *at = malloc(sizeof(AnnotationDefault));
			memset(at, 0, sizeof(AnnotationDefault));
			attrs[i].info = at;
			janno_element_read(bytes + *szread, &at->default_value, &j);
			*szread += j;
		}
		else
		{
			fprintf(stderr, "Attempt read unknown attribute type %s "
					"at %s:%d (skipping)\n", type, __FILE__, __LINE__);
			*szread += attrs[i].len;
		}
	}
}

int jclassfile_read(char *bytes, jcf *cf)
{
	int i = 0;
	cpent *cpentry;
	refinfo *ref;
	nametypeinfo *ntinfo;
	int x;
	int z;

	cf->magic = ntohl(*(int*)(bytes + i));
	i += 4;
	cf->version = ntohl(*(int*)(bytes + i));
	i += 4;
	cf->cpcnt = ntohs(*(short*)(bytes + i));
	i += 2;

	cf->cpool = malloc(cf->cpcnt * sizeof(cpent));
	memset(cf->cpool, 0, cf->cpcnt * sizeof(cpent));

	/* read the constant pool */
	for(z = 1; z < cf->cpcnt; ++z)
	{
		cpentry = &cf->cpool[z];
		cpentry->type = *(bytes + i++);
		cpentry->cpidx = z;

		switch(cpentry->type)
		{
			case CONSTANT_Class:
			case CONSTANT_String:
				cpentry->isheap = 0;
				cpentry->info.idx = ntohs(*(short*)(bytes + i));
				i += 2;
				break;
			case CONSTANT_Fieldref:
			case CONSTANT_Methodref:
			case CONSTANT_InterfaceMethodref:
				cpentry->isheap = 1;
				ref = malloc(sizeof(refinfo));
				ref->classidx = ntohs(*(short*)(bytes + i));
				i += 2;
				ref->nametypeidx = ntohs(*(short*)(bytes + i));
				i += 2;
				cpentry->info.data = ref;
				break;
			case CONSTANT_Integer:
				cpentry->isheap = 0;
				cpentry->info.u4int = ntohl(*(int*)(bytes + i));
				i += 4;
				break;
			case CONSTANT_Float:
				cpentry->isheap = 0;
				memcpy(&cpentry->info.u4float, bytes + i, 4);
				endianswitch((char*)&cpentry->info.u4float, 4);
				i += 4;
				break;
			case CONSTANT_Long:
				cpentry->isheap = 0;
				memcpy(&cpentry->info.u8long, bytes + i, 8);
				endianswitch((char*)&cpentry->info.u8long, 8);
				i += 8;
				z++; /* this takes up two entries */
				break;
			case CONSTANT_Double:
				cpentry->isheap = 0;
				memcpy(&cpentry->info.u8double, bytes + i, 8);
				endianswitch((char*)&cpentry->info.u8double, 8);
				i += 8;
				z++; /* this takes up two entries */
				break;
			case CONSTANT_NameAndType:
				cpentry->isheap = 1;
				ntinfo = malloc(sizeof(nametypeinfo));
				ntinfo->nameidx = ntohs(*(short*)(bytes + i));
				i += 2;
				ntinfo->typeidx = ntohs(*(short*)(bytes + i));
				i += 2;
				cpentry->info.data = ntinfo;
				break;
			case CONSTANT_Utf8:
				cpentry->info.str.len = ntohs(*(short*)(bytes + i));
				i += 2;
				cpentry->info.str.bytes = malloc(cpentry->info.str.len + 1);
				memcpy(cpentry->info.str.bytes, bytes + i,
						cpentry->info.str.len);
				i += cpentry->info.str.len;
				*(cpentry->info.str.bytes + cpentry->info.str.len) = 0;
				break;
			default:
				fprintf(stderr, "Attempt to read unknown constant "
						"type %x (entry #%d) at %s:%d\n",
						cpentry->type, z, __FILE__, __LINE__);
				goto err;
		}
	}

	cf->accflags = ntohs(*(short*)(bytes + i));
	i += 2;
	cf->thiscls = ntohs(*(short*)(bytes + i));
	i += 2;
	cf->supercls = ntohs(*(short*)(bytes + i));
	i += 2;
	cf->ifcnt = ntohs(*(short*)(bytes + i));
	i += 2;

	if(cf->ifcnt)
	{
		cf->ifs = malloc(cf->ifcnt * sizeof(short));
		for(x = 0; x < cf->ifcnt; ++x)
		{
			cf->ifs[x] = ntohs(*(short*)(bytes + i));
			i += 2;
		}
	}
	else
	{
		cf->ifs = NULL;
	}

	cf->fldcnt = ntohs(*(short*)(bytes + i));
	i += 2;

	if(cf->fldcnt)
	{
		cf->flds = malloc(cf->fldcnt * sizeof(fld));
		memset(cf->flds, 0, cf->fldcnt * sizeof(fld));
	}
	else
		cf->flds = NULL;

	for(x = 0; x < cf->fldcnt; ++x)
	{
		cf->flds[x].accflags = ntohs(*(short*)(bytes + i));
		i += 2;
		cf->flds[x].nameidx = ntohs(*(short*)(bytes + i));
		i += 2;
		cf->flds[x].descidx = ntohs(*(short*)(bytes + i));
		i += 2;
		cf->flds[x].attrcnt = ntohs(*(short*)(bytes + i));
		i += 2;

		if(cf->flds[x].attrcnt)
		{
			cf->flds[x].attrs = malloc(cf->flds[x].attrcnt * sizeof(attr));
			jattr_read(cf, bytes + i, cf->flds[x].attrs,
					cf->flds[x].attrcnt, &z);
			i += z;
		}
	}

	cf->methcnt = ntohs(*(short*)(bytes + i));
	i += 2;

	if(cf->methcnt)
	{
		cf->meths = malloc(cf->methcnt * sizeof(meth));
		memset(cf->meths, 0, cf->methcnt * sizeof(meth));
	}
	else
		cf->meths = NULL;

	for(x = 0; x < cf->methcnt; ++x)
	{
		cf->meths[x].accflags = ntohs(*(short*)(bytes + i));
		i += 2;
		cf->meths[x].nameidx = ntohs(*(short*)(bytes + i));
		i += 2;
		cf->meths[x].descidx = ntohs(*(short*)(bytes + i));
		i += 2;
		cf->meths[x].attrcnt = ntohs(*(short*)(bytes + i));
		i += 2;

		cf->meths[x].attrs = malloc(cf->meths[x].attrcnt * sizeof(attr));
		memset(cf->meths[x].attrs, 0, cf->meths[x].attrcnt * sizeof(attr));
		jattr_read(cf, bytes + i, cf->meths[x].attrs,
				cf->meths[x].attrcnt, &z);
		i += z;
	}

	cf->attrcnt = ntohs(*(short*)(bytes + i));
	i += 2;

	cf->attrs = malloc(cf->attrcnt * sizeof(attr));
	memset(cf->attrs, 0, cf->attrcnt * sizeof(attr));
	jattr_read(cf, bytes + i, cf->attrs, cf->attrcnt, &z);
	i += z;


	return 0;

err:
	if (cf->cpool) free (cf->cpool);
	return -1;
}

